﻿using Micro.Extensions.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;

namespace Micro.Extensions.Log
{
    /// <summary>
    /// 实现ILoggerProvider
    /// </summary>
    [ProviderAlias("Micro")]
    public class MicroLoggerProvider : ILoggerProvider
    {

        private readonly MicroLoggerProcessor _loggerProcessor;
        private readonly IFileDiskWriter _fileDiskWriter;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IConfiguration _configuration;
        private readonly IOptionsMonitor<MicroLoggerOptions> _options;


        private readonly ConcurrentDictionary<string, MicroLogger> _loggers = new ConcurrentDictionary<string, MicroLogger>();
        private readonly Func<string, LogLevel, bool> _falseFilter = (cat, level) => false;
        private readonly IDisposable _optionDisposable;



        public MicroLoggerProvider(IOptionsMonitor<MicroLoggerOptions> options, 
            ILoggerWriter loggerWriter,
            IFileDiskWriter fileDiskWriter,
            IHttpContextAccessor httpContextAccessor,
            IConfiguration configuration)
        {
            
            _options = options;
            _fileDiskWriter = fileDiskWriter;
            _httpContextAccessor = httpContextAccessor;
            _configuration = configuration;
            _loggerProcessor = new MicroLoggerProcessor(loggerWriter, _fileDiskWriter, options.CurrentValue.MaxQueuedMessageCount);

            _optionDisposable = options.OnChange(ReloadLoggerOptions);
            //记录当前程序集日志
            LogAssemblyInfo();
        }


        /// <summary>
        /// 记录当前程序集日志
        /// </summary>
        private void LogAssemblyInfo()
        {
            var name = Assembly.GetExecutingAssembly().GetName();
            var assemblyInfo = new AssemblyInfo {Name = name.Name, Version = name.Version.ToString(), CodeBase = name.CodeBase};
            var logger = CreateLogger(name.Name);
            logger.LogAssemblyInfo(assemblyInfo);
        }

        private void ReloadLoggerOptions(MicroLoggerOptions options)
        {
            foreach (var logger in _loggers.Values)
            {
                logger.IsConsole = options.Console;
                logger.Filter = GetFilter(logger.Name, options);
            }
        }

        public ILogger CreateLogger(string name)
        {
            return _loggers.GetOrAdd(name, CreateLoggerImplementation);
        }

        private MicroLogger CreateLoggerImplementation(string name)
        {

            var appId = _configuration["Apollo:AppId"];
            if (string.IsNullOrEmpty(appId))
                appId = _options.CurrentValue.ApplicationId;

            var console = _options.CurrentValue.Console;

            return new MicroLogger(name, appId, console, GetFilter(name, _options.CurrentValue), null, _loggerProcessor, _httpContextAccessor, _fileDiskWriter);
        }

        private Func<string, LogLevel, bool> GetFilter(string name, MicroLoggerOptions options)
        {
            if (options == null) return _falseFilter;
            foreach (var prefix in GetKeyPrefixes(name))
            {
                if (options.LogLevel.TryGetValue(prefix, out var level))
                {
                    return (n, l) => l >= level;
                }
            }

            return _falseFilter;
        }

        private IEnumerable<string> GetKeyPrefixes(string name)
        {
            while (!string.IsNullOrEmpty(name))
            {
                yield return name;
                var lastIndexOfDot = name.LastIndexOf('.');
                if (lastIndexOfDot == -1)
                {
                    yield return "Default";
                    break;
                }
                name = name.Substring(0, lastIndexOfDot);
            }
        }

        public void Dispose()
        {
            _loggerProcessor?.Dispose();
            _optionDisposable.Dispose();
        }
    }
}
